package cluedo;

import static cluedo.PlayerState.RawLtsState.*;
import static cluedo.Player.log;

import java.util.LinkedList;

/**
 * Descrive lo stato assunto da un player.
 * Le transizioni di stato avvengono quando le operazioni che le hanno causate
 * sono state completate.
 * Gli stati in cui gli altri player sono in attesa che questo player compia
 * un'azione sono:
 * <ol>
 * 	 <li>CHOOSING_MY_PAWN
 *   <li>TIE_BREAK
 *   <li>MY_TURN
 *   <li>END_GAME
 * </ol>
 * 
 * E' possibile aggiungere degli StateChangeListener (zero o piu') ad uno stato. Il metodo
 * StateChangeListener.stateChange() viene invocato ad ogni transizione di stato.
 * Non viene pero' invocato durante l'inizializzazione iniziale, anche perche'
 * il costruttore per ora non permette di impostare anche il Listener, che deve
 * quindi essere fatto successivamente.
 */
public class PlayerState implements Cloneable {
	
	/**
	 * true se e' proprietario del gruppo, false altrimenti.
	 * Il suo valore e' valido solo se this.isGroupMember()==true
	 */
	private boolean isOwner= false;
	/**
	 * Nickname identificativo.
	 * Usato per rendere i log su stdout piu' chiari
	 */
	private String nickname;
	
	/**
	 * Stato del LTS
	 */
	private RawLtsState state;
	
	/**
	 * Lista dei StateChangeListener da richiamare quando cambia lo stato
	 */
	private LinkedList<StateChangeListener> listenerList;
	
	/**
	 * Costruttore a solo uso interno
	 * @param nickname nickname usato per rendere i log su stdout piu' chiari
	 * @param initialState stato iniziale
	 * @param owner true se questo e' il proprietario, false altrimenti
	 */
	private PlayerState(String nickname, RawLtsState initialState, boolean owner) {
		this.nickname= nickname;
		this.isOwner= owner;
		this.state= initialState;
		listenerList= new LinkedList<StateChangeListener>();
		log.print("> \"" + this.nickname + "\", stato iniziale: " + this);
	}
	
	/**
	 * Crea lo stato iniziale: NOT_REGISTERED
	 * @param nickname nickname usato per rendere i log su stdout piu' chiari
	 */
	public PlayerState(String nickname) {
		this(nickname, NOT_REGISTERED, false);
	}
	
	/**
	 * Crea uno stato ponendo subito a GROUP_MEMBER e impostando se questo e'
	 * il proprietario o meno del gruppo
	 * @param nickname nickname usato per rendere i log su stdout piu' chiari
	 * @param owner true se questo e' il proprietario, false altrimenti
	 */
	public PlayerState(String nickname, boolean owner) {
		this(nickname, GROUP_MEMBER, owner);
	}
	
	@Override
	public String toString() {
		if (isGroupMember()) {
			if (isOwner) {
				return "(" + state + ", owner)";
			} else {
				return "(" + state + ", not owner)";
			}
		} else {
			return "(" + state + ")";
		}
	}
	
	@Override
	public boolean equals(Object o) {
		
		if (this == o) {
			return true;
		}
		if (this == null) {
			return false;
		}
		if (o instanceof PlayerState) {
			PlayerState p = (PlayerState) o;
			
			return (
				( this.isOwner==p.isOwner ) && 
				( this.nickname.equals(p.nickname) ) &&
				( this.state.equals(p.state) )
			);
		}
		return false;
	}
	
	/**
	 * Stampa su stdout il nuovo stato
	 */
	private void printNewState() {
		log.print("> \"" + this.nickname + "\", nuovo stato: " + this);
	}
	
	/**
	 * Restituisce lo stato "grezzo" del LTS, ovvero che non prende in considerazione
	 * se questo e' proprietario o meno di un gruppo.
	 * Non abusare di questo metodo e preferire l'uso dei metodi isXXX(). Eventualmente
	 * aggiungerne di ulteriori.
	 * @return lo stato grezzo
	 */
	public RawLtsState getRawLtsState() {
		return state;
	}
	
	/**
	 * Chiama tutti i StateChangeListener registrati
	 */
	private void fireStateChange(PlayerState previousState) {
		synchronized (listenerList) {
			for (StateChangeListener entry : listenerList) {
				entry.stateChange(previousState);
			}
		}
	}
	
	public void addStateChangeListener(StateChangeListener listener) {
		synchronized (listenerList) {
			listenerList.add(listener);
		}
	}
	
	public void removeStateChangeListener(StateChangeListener listener) {
		synchronized (listenerList) {
			listenerList.remove(listener);
		}
	}
	
	// VERIFICA DI APPARTENZA AD UN INSIEME DI STATI

	/** 
	 * Ritorna true se e' stata fatta la registrazione presso il Registrazione
	 * Server, false altrimenti
	 * @return vedi descrizione
	 */
	public boolean isRegistered() {
		if (state==NOT_REGISTERED) {
			return false;
		} else {
			return true;
		}
	}
	
	/**
	 * Ritorna true se e' membro di un gruppo, ma la partita non e' ancora 
	 * stata iniziata
	 * @return vedi descrizione
	 */
	public boolean isWaitingToStart() {
		if (state==GROUP_MEMBER) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Restituisce true se sono in corso i preparativi, cioe' la partita' e' stata
	 * iniziata ma non si sta ancora giocando. In questa fase avviene la scelta
	 * del colore, la distribuzione delle carte e la scelta del primo giocatore
	 * @return
	 */
	public boolean isMakeingPreparations() {
		if (
				state==WAITING_BEFORE_PAWN ||
				state==CHOOSING_MY_PAWN ||
				state==WAITING_AFTER_PAWN ||
				state==TIE_BREAK ||
				state==WAITING_TIEBREAK) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Restituisce true se si sta giocando una partita, ovvero se e' il turno
	 * di questo o di un altro giocatore.
	 * @return vedi descrizione
	 */
	public boolean isPlaying() {
		if (state==MY_TURN || state==NOT_MY_TURN || state==REFUTE_ASSUMPTION || state==WAITING_REFUTE) return true;
		else return false;
	}
	
	/**
	 * Restituisce true se la partita e' terminata per questo giocatore. Nota
	 * che se questo giocatore ha terminato perche' ha perso, altri giocatori 
	 * potrebbero continuare a giocare.
	 * @return vedi descrizione
	 */
	public boolean isGameEnded() {
		return (state==END_GAME);
	}
	
	/** 
	 * Ritorna true se la partita e' stata iniziata, false altrimenti.
	 * Equivale a (isMakeingPreparations() || isPlaying() || isGameEnded())
	 * @return vedi descrizione
	 */
	public boolean isPlayStarted() {
		return (isMakeingPreparations() || isPlaying() || isGameEnded());
	}
	
	/** 
	 * Ritorna true se e' membro (proprietario o meno) di un gruppo, false altrimenti
	 * @return vedi descrizione
	 */
	public boolean isGroupMember() {
		if (
				state==GROUP_MEMBER ||
				isPlayStarted()) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Restituisce true se il player e' proprietario del gruppo, 
	 * false se e' non proprietario o non membro di alcun gruppo
	 * @return vedi descrizione
	 */
	public boolean isGroupOwner() {
		if (!isGroupMember()) {
			return false;
		} else {
			return isOwner;
		}
	}
	
	/**
	 * Restituisce true se e' il turno di questo giocatore, false altrimenti
	 * @return vedi descrizione
	 */
	public boolean isMyTurn() {
		if (state==MY_TURN) return true;
		else return false;
	}
	
	/**
	 * Restituisce true se non e' il turno di questo giocatore, false altrimenti
	 * @return vedi descrizione
	 */
	public boolean isNotMyTurn() {
		if (state==NOT_MY_TURN) return true;
		else return false;
	}
	
	/**
	 * Restituisce true se il giocatore sta attendendo che gli altri player
	 * gli comunichino se possono confutare l'ipotesi oppure no
	 * @return vedi descrizione
	 */
	public boolean iAmWaitingRefute() {
		if (state==WAITING_REFUTE) return true;
		else return false;
	}
	
	/**
	 * E' stata formulata una ipotesi e il giocatore deve confutarla
	 * @return vedi descrizione
	 */
	public boolean iAmRefutingAssumption() {
		if (state==REFUTE_ASSUMPTION) return true;
		else return false;
	}
	
	/**
	 * Restituisce true se questo player e' membro di un gruppo (proprietario o
	 * meno) ed e' in attesa che qualche altro player compia un'azione.
	 * @return vedi descrizione
	 */
	public boolean iAmWaitingSomebody() {
		if (isGroupOwner()) {
			// sono proprietario
			return (
				state==WAITING_BEFORE_PAWN || // in realta' il proprietario non va mai in questo stato
				state==WAITING_AFTER_PAWN ||
				state==WAITING_TIEBREAK ||
				state==NOT_MY_TURN ||
				state==WAITING_REFUTE
			);
		} else if (isGroupMember()) {
			// sono membro ma non proprietario
			return (
					state==GROUP_MEMBER || // attendo che inizi la partita
					state==WAITING_BEFORE_PAWN || 
					state==WAITING_AFTER_PAWN ||
					state==TIE_BREAK || // implicitamente attendo che il proprietario tiri il dado (non si sa mai che nel frattempo sia morto)
					state==WAITING_TIEBREAK ||
					state==NOT_MY_TURN ||
					state==WAITING_REFUTE
				);
		} else {
			// non sono membro
			return false;
		}
	}
	
	

	/**
	 * Restituisce true se il player sta aspettando che gli altri player tirino il dado
	 * per lo spareggio
	 * @return vedi descrizione
	 */
	public boolean iAmWaitingTieBreak() {
		return state==WAITING_TIEBREAK;
	}
	
	
	// AZIONI DEL LTS
	
	/**
	 * E' stata eseguita la registrazione sul Registration Server
	 */
	public void register() {
		if (state==NOT_REGISTERED) {
			PlayerState prev= (PlayerState) this.clone();
			state= ALONE;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	/**
	 * Questo player ha creato un proprio gruppo
	 */
	public void createGroup() {
		if (state==ALONE) {
			PlayerState prev= (PlayerState) this.clone();
			state= GROUP_MEMBER;
			isOwner= true;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	/**
	 * Questo player si e' unito ad un gruppo pre-esistente (appartenente ad un
	 * altro player)
	 */
	public void joinGroup() {
		if (state==ALONE) {
			PlayerState prev= (PlayerState) this.clone();
			state= GROUP_MEMBER;
			isOwner= false;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}

	/**
	 * La partita e' iniziata e questo player ha ricevuto le carte e non e' 
	 * quello che le ha distribuite.
	 */
	public void setDeck() {
		if (state==GROUP_MEMBER) {
			PlayerState prev= (PlayerState) this.clone();
			state= WAITING_BEFORE_PAWN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	/**
	 * La partita e' iniziata e questo player ha distribuito le carte.
	 * Nota che chi ha distributo le carte passa direttamente allo stato CHOOSING_MY_PAWN 
	 * perche' e' lui il primo a scegliere la pedina.
	 * Non controlla se questo stato corrisponde realmente a quello del
	 * proprietario
	 */
	public void allSetDeck() {
		if (state==GROUP_MEMBER) {
			PlayerState prev= (PlayerState) this.clone();
			state= CHOOSING_MY_PAWN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	/**
	 * Il player precedente a questo ha scelto la sua pedina. Ora questo player 
	 * puo' scegliere la propria pedina.
	 */
	public void choosingMyPawn() {
		if (state==WAITING_BEFORE_PAWN) {
			PlayerState prev= (PlayerState) this.clone();
			state= CHOOSING_MY_PAWN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	/**
	 * Questo player ha scelto la propria pedina
	 */
	public void waitingAfterPawn() {
		if (state==CHOOSING_MY_PAWN) {
			PlayerState prev= (PlayerState) this.clone();
			state= WAITING_AFTER_PAWN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	/**
	 * Nessuno ha Miss Scarlett.
	 * Questo player deve fare lo spareggio per il primo giocatore che inizia
	 * a giocare.
	 */
	public void doTieBreak() {
		if (state==WAITING_AFTER_PAWN ||
				state==WAITING_TIEBREAK) {
			PlayerState prev= (PlayerState) this.clone();
			state= TIE_BREAK;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	/**
	 * Nessuno ha Miss Scarlett.
	 * Questo player non e' coinvolto nello spareggio per il primo giocatore che inizia
	 * a giocare.
	 */
	public void doNotTieBreak() {
		if (state==WAITING_AFTER_PAWN) {
			PlayerState prev= (PlayerState) this.clone();
			state= WAITING_TIEBREAK;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}

	/**
	 * Io sono Miss Scarlett: il primo turno e' mio
	 */
	public void iAmMissScarlett() {
		if (state==WAITING_AFTER_PAWN) {
			PlayerState prev= (PlayerState) this.clone();
			state= MY_TURN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}

	/**
	 * Un altro player e' Miss Scarlett: il primo turno e' suo
	 */
	public void thereIsMissScarlett() {
		if (state==WAITING_AFTER_PAWN) {
			PlayerState prev= (PlayerState) this.clone();
			state= NOT_MY_TURN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}

	/**
	 * Questo player ha tirato per lo spareggio e attende che gli altri facciano
	 * altrettanto.
	 */
	public void waitingTiebreak() {
		if (state==TIE_BREAK) {
			PlayerState prev= (PlayerState) this.clone();
			state= WAITING_TIEBREAK;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}

	/**
	 * Ho vinto lo spareggio: il primo turno e' mio
	 */
	public void wonTiebreak() {
		if (state==WAITING_TIEBREAK) {
			PlayerState prev= (PlayerState) this.clone();
			state= MY_TURN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	/**
	 * Ho perso lo spareggio: il primo turno e' di un altro
	 */
	public void loseTiebreak() {
		if (state==WAITING_TIEBREAK) {
			PlayerState prev= (PlayerState) this.clone();
			state= NOT_MY_TURN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}

	/**
	 * E' diventato il mio turno
	 */
	public void myTurn() {
		if (state==NOT_MY_TURN) {
			PlayerState prev= (PlayerState) this.clone();
			state= MY_TURN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}

	/**
	 * Ho finito il mio turno
	 */
	public void endTurn() {
		if (state==MY_TURN) {
			PlayerState prev= (PlayerState) this.clone();
			state= NOT_MY_TURN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	/**
	 * Ho ricevuto l'ipotesi di un altro player
	 * Devo controllare se posso confutarla
	 */
	public void getAssumption() {
		if (state==NOT_MY_TURN) {
			PlayerState prev= (PlayerState) this.clone();
			state= REFUTE_ASSUMPTION;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	/**
	 * Ho comunicato a chi ha fatto l'ipotesi se posso confutarla
	 */
	public void answerRefute() {
		if (state==REFUTE_ASSUMPTION) {
			PlayerState prev= (PlayerState) this.clone();
			state= NOT_MY_TURN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	
	/**
	 * Ho inviato agli altri player la mia ipotesi affinche' verifichino se possono confutarla
	 */
	public void sendAssumption() {
		if (state==MY_TURN) {
			PlayerState prev= (PlayerState) this.clone();
			state= WAITING_REFUTE;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	/**
	 * Ho ricevuto tutte le risposte riguardo l'ipotesi che ho effettuato
	 */
	public void getAnswers() {
		if (state==WAITING_REFUTE) {
			PlayerState prev= (PlayerState) this.clone();
			state= MY_TURN;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	

	/**
	 * Ho finito la mia partita
	 */
	public void endGame() {
		if (state==MY_TURN || state==NOT_MY_TURN) {
			PlayerState prev= (PlayerState) this.clone();
			state= END_GAME;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	public void newGame() {
		if (state==END_GAME) {
			PlayerState prev= (PlayerState) this.clone();
			state= GROUP_MEMBER;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}


	// AZIONI SPECIALI

	/**
	 * I preparativi della partita sono stati interrotti a causa dell'uscita di
	 * tutti gli altri player. Questo player continua pero' a rimanere nel gruppo
	 * di cui faceva parte. Oppure ho vinto ancor prima di visualizzare la finestra di gioco
	 * perche' gli altri hanno fatto crash.
	 * Se non sono in questi casi non eseguo niente
	 */
	public void abortNewPlay() {
		if (isMakeingPreparations() || isGameEnded()) {
			PlayerState prev= (PlayerState) this.clone();
			state=GROUP_MEMBER;
			printNewState();
			fireStateChange(prev);
		}
	}
	
	/**
	 * Diventa proprietario.
	 * Puo' essere fatto in qualsiasi stato.
	 * Se this.isGroupMember()==false non esegue alcuna operazione
	 */
	public void gainOwnership() {
		if (isGroupMember()) {
			PlayerState prev= (PlayerState) this.clone();
			isOwner= true;
			printNewState();
			fireStateChange(prev);
		}
	}

	/**
	 * Perdi la proprieta' del gruppo.
	 * Puo' essere fatto in qualsiasi stato.
	 * Se this.isGroupMember()==false non esegue alcuna operazione
	 */
	public void loseOwnership() {
		if (isGroupMember()) {
			PlayerState prev= (PlayerState) this.clone();
			isOwner= false;
			printNewState();
			fireStateChange(prev);
		}
	}
	
	/**
	 * E' stata eliminata la registrazione dul Registration Server.
	 * Puo' essere fatto in qualsiasi stato.
	 */
	public void unregister() {
		PlayerState prev= (PlayerState) this.clone();
		state= NOT_REGISTERED;
		printNewState();
		fireStateChange(prev);
	}
	
	/**
	 * Questo player e' uscito dal gruppo di cui faceva parte
	 */
	public void exitGroup() {
		if (isGroupMember()) {
			PlayerState prev= (PlayerState) this.clone();
			state= ALONE;
			printNewState();
			fireStateChange(prev);
		} else {
			throw genericException();
		}
	}
	
	// STATI DEL LTS
	
	/**
	 * Insieme grezzo degli stati dell LTS del player.
	 */
	public enum RawLtsState {
		// NOT_REGISTERED,ALONE,GROUP_MEMBER,WAITING_BEFORE_PAWN,CHOOSING_MY_PAWN,
		// WAITING_AFTER_PAWN,TIE_BREAK,WAITING_TIEBREAK,MY_TURN,NOT_MY_TURN,END_GAME;

		/**
		 * Il player non e' registrato presso il Registration Server.
		 * In tutti gli altri stati il player e' registrato
		 */
		NOT_REGISTERED,
		/**
		 * Il player non fa parte di alcun gruppo
		 */
		ALONE,
		/**
		 * Il player e' membro (proprietario o meno) del gruppo e la partita non e'
		 * iniziata
		 */
		GROUP_MEMBER,
		/**
		 * Il player sta aspettando che i player precedenti scelgano la propria pedina
		 */
		WAITING_BEFORE_PAWN,
		/**
		 * Il player (l'utente umano) sta scegliendo la pedina.
		 */
		CHOOSING_MY_PAWN,
		/**
		 * Il player sta aspettando che i player seguenti a lui scelgano la propria pedina
		 */
		WAITING_AFTER_PAWN,
		/**
		 * E' necessario fare lo spareggio.
		 * Il player (l'utente umano) deve tirare il dado
		 */
		TIE_BREAK,
		/**
		 * Il player aspetta che gli altri player lancino il dado del tie-break
		 */
		WAITING_TIEBREAK,
		/**
		 * E' il turno di questo giocatore
		 */
		MY_TURN,
		/**
		 * E' il turno di questo giocatore e sta attendendo che gli altri player gli dicano se possono confutare la sua ipotesi
		 */
		WAITING_REFUTE,
		/**
		 * Non e' il turno di questo giocatore
		 */
		NOT_MY_TURN,
		/**
		 * Non e' il turno di questo giocatore e deve confutare un'ipotesi
		 */
		REFUTE_ASSUMPTION,
		/**
		 * Qualcuno ha vinto
		 */
		END_GAME;
	}
	
	private RuntimeException genericException() {
		return new RuntimeException("\"" + nickname + "\", stato attuale " + this 
				+ ": transizione di stato non permessa");
	}

	@Override
	public Object clone() {
		try {
			PlayerState o = (PlayerState) super.clone();
			o.listenerList= new LinkedList<StateChangeListener>();
			
			return o;
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
			return null;
		}
	}
}
